﻿using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http.Cors;
using Sustainalytics.Auth.Claims;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.Utils;
using Sustainalytics.ClientDW.Entities;
using System.Web.Http;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.ESGRatings.Service.Models;
using MongoDB.Bson;
using System.Text.RegularExpressions;
using System.Linq.Expressions;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;

namespace Sustainalytics.ESGRatings.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/companies/esglisting")]
    public class ListingController : ApiController
    {
        private IMongoCollection<CorporateData> _corporateDataCollection;
        private IMongoCollection<RPCompanyESGComputationResult> _rpCompanyEsgComputatonResultCollection;
        private IMongoCollection<EsgFocusThresholds> _esgFocusThresholdsCollection;
        private IMongoCollection<RPCompanyProfile> _rpCompanyProfileCollection;

        public ListingController(
           IMongoCollection<CorporateData> corporateDataCollection,
           IMongoCollection<RPCompanyESGComputationResult> rpCompanyEsgComputatonResultCollection,
           IMongoCollection<EsgFocusThresholds> esgFocusThresholdsCollection,
           IMongoCollection<RPCompanyProfile> rpCompanyProfileCollection)
        {
            _corporateDataCollection = corporateDataCollection;
            _rpCompanyEsgComputatonResultCollection = rpCompanyEsgComputatonResultCollection;
            _esgFocusThresholdsCollection = esgFocusThresholdsCollection;
            _rpCompanyProfileCollection = rpCompanyProfileCollection;
        }

        public ListingController()
        {
            Setup();
        }

        private void Setup()
        {
            string connectionString = ClientDWConnection.GetConnectionString();
            _corporateDataCollection = MongoFactory.GetCollectionFromDatabase<CorporateData>(connectionString);
            _rpCompanyEsgComputatonResultCollection = MongoFactory.GetCollectionFromDatabase<RPCompanyESGComputationResult>(connectionString);
            _esgFocusThresholdsCollection = MongoFactory.GetCollectionFromDatabase<EsgFocusThresholds>(connectionString);
            _rpCompanyProfileCollection = MongoFactory.GetCollectionFromDatabase<RPCompanyProfile>(connectionString);
        }

        private List<FilterDefinition<CorporateData>> getFilterOptionsForCompanyData(EsgRatingListFilters filters)
        {
            var filtersCompanies = Builders<CorporateData>.Filter;
            var esgFiltersList = new List<FilterDefinition<CorporateData>> { FilterDefinition<CorporateData>.Empty };

            if (!filters.CompanyName.IsNullOrEmpty())
            {
                var filterCompany = filtersCompanies.Regex("CompanyName", new BsonRegularExpression(Regex.Escape(filters.CompanyName.ToLowerInvariant()), "i"));
                esgFiltersList.Add(filterCompany);
            }

            if (!filters.SubPeerGroup.IsNullOrEmpty())
            {
                var filterSubPeerGroup = filtersCompanies.In(x => x.SubPeerGroup, filters.SubPeerGroup);
                esgFiltersList.Add(filterSubPeerGroup);
            }

            return esgFiltersList;
        }

        private List<FilterDefinition<RPCompanyESGComputationResult>> getFilterOptionsForAssessment(EsgRatingListFilters filters)
        {
            var filtersCompanies = Builders<RPCompanyESGComputationResult>.Filter;
            var esgFiltersList = new List<FilterDefinition<RPCompanyESGComputationResult>> { FilterDefinition<RPCompanyESGComputationResult>.Empty };

            if (!filters.Assessments.IsNullOrEmpty())
            {
                var filterAssessments = filtersCompanies.In(x => x.OverallPerformance.Assessment.Name, filters.Assessments);
                esgFiltersList.Add(filterAssessments);
            }

            return esgFiltersList;
        }

        private static readonly Dictionary<string, Expression<Func<CorporateData, object>>> sortDictionary = new Dictionary<string, Expression<Func<CorporateData, object>>>
        {
            { "CompanyName", x => x.CompanyName },
            { "Exchange", x => x.MainExchange },
            { "Ticker", x => x.MainTicker },
            { "SubPeerGroup", x => x.SubPeerGroup },
            { "LastUpdate", x => x.LastControversyUpdate },
            { "Assessment", x => x.CompanyName }
        };


        [Route("nomenclaturesList")]
        public async Task<IHttpActionResult> Get(string nomenclature = "subIndustries")
        {
            if (nomenclature == "subIndustries")
            {
                var subIndustryList = await _corporateDataCollection
               .Aggregate()
               .Group(x => x.SubPeerGroup, y => new { y.Key })
               .ToListAsync().ConfigureAwait(false);

                var result = subIndustryList.OrderBy(x => x.Key).Select(x => new
                {
                    id = x.Key,
                    name = x.Key
                });

                return Ok(result);
            }
            else if (nomenclature == "assessments")
            {
                var assessmentsList = await _esgFocusThresholdsCollection
                .Aggregate()
                .Group(x => x.Value, y => new { y.Key })
                .ToListAsync().ConfigureAwait(false);

                var result = assessmentsList.Select(x => new
                {
                    id = x.Key,
                    name = x.Key
                });

                return Ok(result);
            }
            return BadRequest("No nomenclature provided");
        }

        [Route("")]
        [InjectUniverse("universeIds", AccountFeature.EsgRatingsOnlineReport)]
        public async Task<IHttpActionResult> Get(
            [FromUri] EsgRatingListFilters filters,
            int skip = 0,
            int take = 100,
            bool getCount = false,
            bool orderAscending = true,
            string orderBy = "CompanyName",
            [FromUri]int[] universeIds = null)
        {
            var filterOptionsForCompanyData = getFilterOptionsForCompanyData(filters);
            FindOptions options = new FindOptions() { Collation = new Collation(locale: "en", caseLevel: true, strength: CollationStrength.Primary) };

            var filterOptionsForAssessments = getFilterOptionsForAssessment(filters);

            var companiesPerformanceAssessments = await _rpCompanyEsgComputatonResultCollection
                 .Find(Builders<RPCompanyESGComputationResult>.Filter.And(filterOptionsForAssessments))
                 .Project<RPCompanyESGComputationResult>(Builders<RPCompanyESGComputationResult>.Projection
                 .Include(x => x.Id)
                 .Include(x => x.OverallPerformance.Assessment.Name))
                 .ToListAsync().ConfigureAwait(false);

            var filteredCompaniesAssessmentsPerformance = companiesPerformanceAssessments.Select(x => x.Id).ToList();

            var builderCorporateData = Builders<CorporateData>.Filter;
            var filter = (builderCorporateData.Eq(x => x.CoreEsgResearchState.ResearchState, ResearchState.ResearchAvailable) |
                    builderCorporateData.Eq(x => x.ComprehensiveEsgResearchState.ResearchState, ResearchState.ResearchAvailable)) &
                    builderCorporateData.And(filterOptionsForCompanyData) &
                    builderCorporateData.In(x => x.Id, filteredCompaniesAssessmentsPerformance);

            var companiesCollection = _corporateDataCollection
                .Find(filter, options)
                .Project<CorporateData>(Builders<CorporateData>.Projection
                    .Include(x => x.Id)
                    .Include(x => x.CompanyName)
                    .Include(x => x.ComprehensiveEsgResearchState.ResearchState)
                    .Include(x => x.CoreEsgResearchState.ResearchState)
                    .Include(x => x.MainExchange)
                    .Include(x => x.MainTicker)
                    .Include(x => x.SubPeerGroup)
                    .Include(x => x.UniverseIds));

            if (getCount)
            {
                // get companies paged
                var count = await _corporateDataCollection
                    .Find(filter, options)
                    .CountAsync()
                    .ConfigureAwait(false);

                return Ok(new { companyCount = count });
            }

            var sortDefinition = orderAscending ?
                Builders<CorporateData>.Sort.Ascending(sortDictionary[orderBy]) :
                Builders<CorporateData>.Sort.Descending(sortDictionary[orderBy]);

            var companies = await companiesCollection
                .Sort(sortDefinition)
                .Skip(skip)
                .Limit(take)
                .ToListAsync()
                .ConfigureAwait(false);

            var companiesIds = companies.Select(i => i.Id);

            var companiesLastUpdates = await _rpCompanyProfileCollection
             .Find(Builders<RPCompanyProfile>.Filter.In(x => x.Id, companiesIds))
             .Project<RPCompanyProfile>(Builders<RPCompanyProfile>.Projection
                 .Include(x => x.Id)
                 .Include(x => x.LastUpdate)
                 .Include(x => x.LastFullReviewDate))
                 .ToListAsync()
                 .ConfigureAwait(false); ;

            var result = companies.Select(x => new
            {
                x.Id,
                x.CompanyName,
                Exchange = x.MainExchange,
                Ticker = x.MainTicker,
                x.SubPeerGroup,
                LastUpdate = companiesLastUpdates.Where(p => p.Id == x.Id).Select(rpCompany => x.CoreEsgResearchState.ResearchState == ResearchState.ResearchAvailable ? rpCompany.LastUpdate : rpCompany.LastFullReviewDate),
                Assessment = companiesPerformanceAssessments.Where(p => p.Id == x.Id).Select(perf => perf.OverallPerformance.Assessment.Name),
                IsEnabled = universeIds.Any(y => x.UniverseIds.Contains(y))
            });

            return Ok(result);
        }
    }

}
